package com.isyscore.os.metadata.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.isyscore.boot.mybatis.PageRequest;
import com.isyscore.os.core.exception.DataFactoryException;
import com.isyscore.os.metadata.common.CommonService;
import com.isyscore.os.metadata.dao.DatamodelMapper;
import com.isyscore.os.metadata.model.dto.DataModelColumnDTO;
import com.isyscore.os.metadata.model.dto.DataModelDTO;
import com.isyscore.os.metadata.model.dto.DataSourceDTO;
import com.isyscore.os.metadata.model.dto.QuerySqlResultDTO;
import com.isyscore.os.metadata.model.dto.req.DataModelCreateOrUpdateReq;
import com.isyscore.os.metadata.model.entity.*;
import com.isyscore.os.metadata.model.vo.DataQueryVO;
import com.isyscore.os.metadata.service.DataBuildService;
import com.isyscore.os.metadata.utils.ColumnUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.isyscore.os.core.exception.ErrorCode.*;
import static java.util.Collections.emptyList;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author
 * @since 2021-08-11
 */
@Service
@Transactional()
public class DatamodelService extends CommonService<DatamodelMapper, Datamodel> {

    @Autowired
    private SqlStatementService sqlStatementService;

    @Autowired
    private ErRelationService erRelationService;

    @Autowired
    private DatamodelColumnService datamodelColumnService;

    @Autowired
    private MetricService metricService;

    @Autowired
    private MetricDimFilterService metricDimFilterService;

    @Autowired
    private MetricMeasureService metricMeasureService;

    @Autowired
    private DataSourceServiceImpl dataSourceService;

    @Autowired
    private DataBuildService dataBuildService;

    public Datamodel getDatamodelById(Long id) {
        Datamodel datamodel = this.getById(id);
        if (datamodel == null) {
            throw new DataFactoryException(DATA_NOT_FOUND, "该数据模型不存在，请刷新页面后重试");
        }
        return datamodel;
    }

    public Long addDatamodel(DataModelCreateOrUpdateReq req) {
        Long sqlId = null;
        Datamodel datamodel = new Datamodel();
        if (req.getCreateType() == 1) {
            if (Strings.isNullOrEmpty(req.getSql())) {
                throw new DataFactoryException(PARAM_ERROR);
            }
            sqlId = sqlStatementService.saveSql(req.getSql());
            datamodel.setSqlRefId(sqlId);
        } else {
            DataSourceDTO ds = dataSourceService.getDataSource(req.getDsId());
            String erSql = erRelationService.parseSQLFromERrelations(req.getDbName(), ds, req.getErRelation());
            sqlId = sqlStatementService.saveSql(erSql);
            datamodel.setSqlRefId(sqlId);
            ErRelation erRelation = new ErRelation();
            erRelation.setErLayoutData(req.getErRelation().getErLayoutJSON());
            erRelationService.save(erRelation);
            datamodel.setErRefId(erRelation.getId());
        }
        datamodel.setDsRefId(req.getDsId());
        datamodel.setDbName(req.getDbName());
        datamodel.setName(req.getName());
        datamodel.setDescription(req.getDescription());
        datamodel.setCreateType(req.getCreateType());
        this.save(datamodel);
        List<DatamodelColumn> columns = parseDatamodelColumnsFromDto(datamodel.getId(), req.getColums());
        datamodelColumnService.saveBatch(columns);
        return datamodel.getId();
    }

    public DataModelDTO getDatamodelDetailById(Long id) {
        Datamodel datamodel = this.getDatamodelById(id);
        DataModelDTO dataModelDetail = new DataModelDTO();
        dataModelDetail.fromDataModleEntity(datamodel);
        String sql = sqlStatementService.getSqlById(datamodel.getSqlRefId());
        dataModelDetail.setSql(sql);
        if (datamodel.getCreateType() == 2) {
            dataModelDetail.setErLayoutJSON(erRelationService.getErRelationById(datamodel.getErRefId()));
        }
        List<DataModelColumnDTO> columnDtoList = Lists.newArrayList();
        List<DatamodelColumn> columns = datamodelColumnService.getColumnsByDataModelId(datamodel.getId());
        columns.forEach(col -> {
            DataModelColumnDTO dc = new DataModelColumnDTO();
            dc.setColName(col.getColName());
            dc.setColLabel(col.getColLabel());
            dc.setColType(col.getColType());
            dc.setColDataType(col.getColDataType());
            columnDtoList.add(dc);
        });
        dataModelDetail.setColumns(columnDtoList);
        return dataModelDetail;
    }

    public void removeDatamodel(Long id) {
        Datamodel datamodel = this.getDatamodelById(id);
        Integer num = metricService.countMetricByDatamodel(datamodel.getId());
        if (num > 0) {
            throw new DataFactoryException(DATA_NOT_ALLOWED_DELETE, "数据模型仍然被部分指标引用，无法删除");
        }
        sqlStatementService.removeById(datamodel.getSqlRefId());
        if (datamodel.getErRefId() != null) {
            erRelationService.removeById(datamodel.getErRefId());
        }
        datamodelColumnService.removeColumnsByDataModelId(datamodel.getId());
        this.removeById(datamodel.getId());
    }

    public void updateDatamodel(Long id, DataModelCreateOrUpdateReq req) {
        Datamodel currentdata = getDatamodelById(id);
        List<DatamodelColumn> originCols = datamodelColumnService.getColumnsByDataModelId(currentdata.getId());
        List<DatamodelColumn> newColumns = parseDatamodelColumnsFromDto(currentdata.getId(), req.getColums());
        boolean canUpdate = checkDataModelCanUpdate(currentdata.getId(), originCols, newColumns);
        if (!canUpdate) {
            throw new DataFactoryException(DATAMODEL_NOT_ALLOWED_MODIFY);
        }
        currentdata.setName(req.getName());
        currentdata.setDescription(req.getDescription());
        currentdata.setDsRefId(req.getDsId());
        currentdata.setDbName(req.getDbName());
        currentdata.setCreateType(req.getCreateType());
        String sql;
        if (req.getCreateType() == 1) {
            if (Strings.isNullOrEmpty(req.getSql())) {
                throw new DataFactoryException(PARAM_ERROR);
            }
            sql = req.getSql();
        } else {
            if (req.getErRelation() == null) {
                throw new DataFactoryException(PARAM_ERROR);
            }
            DataSourceDTO ds = dataSourceService.getDataSource(req.getDsId());
            sql = erRelationService.parseSQLFromERrelations(currentdata.getDbName(), ds, req.getErRelation());
            erRelationService.removeById(currentdata.getErRefId());
            ErRelation erRelation = new ErRelation();
            erRelation.setErLayoutData(req.getErRelation().getErLayoutJSON());
            erRelationService.save(erRelation);
            currentdata.setErRefId(erRelation.getId());
        }
        sqlStatementService.updateSql(currentdata.getSqlRefId(), sql);
        this.updateById(currentdata);
        //同步更新模型涉及到的指标
        List<Metric> metrics = metricService.list(Wrappers.lambdaQuery(Metric.class).eq(Metric::getDatamodelRefId, currentdata.getId()));
        for (Metric metric : metrics) {
            sqlStatementService.updateSql(metric.getSqlRefId(), metricService.parseMetricSql(metric));
        }
        //更新模型的字段
        datamodelColumnService.removeColumnsByDataModelId(currentdata.getId());
        datamodelColumnService.saveBatch(newColumns);
    }

    public IPage<Map<String, Object>> queryDatamodelResult(long dataModelId, PageRequest pr) {
        Datamodel dm = this.getDatamodelById(dataModelId);
        String sql = sqlStatementService.getSqlById(dm.getSqlRefId());
        DataQueryVO req = new DataQueryVO();
        req.setSqlText(sql);
        req.setDatabaseName(dm.getDbName());
        req.setDataSourceId(dm.getDsRefId());
        req.setCurrent(pr.getCurrent());
        req.setSize(pr.getSize());
        req.setSearchCount(pr.isSearchCount());
        QuerySqlResultDTO sqlResultDto = dataBuildService.executeQuerySqlSync(req);
        Page<Map<String, Object>> page = new Page<>(pr.getCurrent(), pr.getSize(), sqlResultDto.getTotal());
        page.setRecords(sqlResultDto.getResults());
        return page;
    }

    public void importData(List<Datamodel> datas) {
        if (datas.isEmpty()) {
            return;
        }
        Set<Long> datamodelIds = Sets.newConcurrentHashSet();
        for (Datamodel datamodel : datas) {
            datamodelIds.add(datamodel.getId());
        }
        //删除模型
        LambdaQueryWrapper<Datamodel> datamodelQw = new LambdaQueryWrapper<>();
        datamodelQw.in(Datamodel::getId, datamodelIds);
        this.remove(datamodelQw);
        this.saveBatch(datas);
    }

    public List<Datamodel> getDatamodelByIds(Set<Long> datamodelIds) {
        if (datamodelIds.isEmpty()) {
            return emptyList();
        }
        LambdaQueryWrapper<Datamodel> datamodelQw = new LambdaQueryWrapper<>();
        datamodelQw.in(Datamodel::getId, datamodelIds);
        return this.list(datamodelQw);
    }

    private boolean checkDataModelCanUpdate(Long datamodelId, List<DatamodelColumn> originCols, List<DatamodelColumn> updatedCols) {
        List<Metric> metrics = metricService.getMetricByDatamodel(datamodelId);
        Set<String> usedColumns = Sets.newHashSet();
        Set<Long> metricIds = Sets.newHashSet();
        for (Metric metric : metrics) {
            if (!Strings.isNullOrEmpty(metric.getDimension())) {
                usedColumns.add(metric.getDimension());
            }
            if (!Strings.isNullOrEmpty(metric.getTimePeriodDim())) {
                usedColumns.add(metric.getTimePeriodDim());
            }
            if (!metric.isExtended()) {
                List<MetricMeasure> measures = metricMeasureService.getMeasuresByMetricId(metric.getId());
                for (MetricMeasure measure : measures) {
                    usedColumns.add(measure.getMeasureColName());
                }
            }
            metricIds.add(metric.getId());
        }
        usedColumns.addAll(metricDimFilterService.getUsedFilterColumnNames(metricIds));
        Map<String, DatamodelColumn> originColMap = originCols.stream().collect(Collectors.toMap(DatamodelColumn::getColName, col -> col));
        Map<String, DatamodelColumn> updatedColMap = updatedCols.stream().collect(Collectors.toMap(DatamodelColumn::getColName, col -> col));
        boolean canUpdate = true;
        for (String metricCol : usedColumns) {
            if (!updatedColMap.containsKey(metricCol)) {
                canUpdate = false;
                break;
            }
            DatamodelColumn originCol = originColMap.get(metricCol);
            DatamodelColumn updatedCol = updatedColMap.get(metricCol);
            if (!originCol.getColType().equals(updatedCol.getColType())
                    || !originCol.getColDataType().equals(updatedCol.getColDataType())) {
                canUpdate = false;
                break;
            }
        }
        return canUpdate;
    }


    private List<DatamodelColumn> parseDatamodelColumnsFromDto(Long datamodelId, List<DataModelColumnDTO> columnDtos) {
        if (columnDtos == null || columnDtos.isEmpty()) {
            return emptyList();
        }
        List<DatamodelColumn> columns = Lists.newArrayList();
        Set<String> duplicateColumns = ColumnUtil.getDuplicateColumnsForDataModelColumn(columnDtos);
        columnDtos.forEach(col -> {
            DatamodelColumn dc = new DatamodelColumn();
            dc.setColName(ColumnUtil.getColName(duplicateColumns, col.getColName(), col.getTableName()));
            dc.setColLabel(col.getColLabel());
            dc.setColType(col.getColType());
            dc.setColDataType(col.getColDataType());
            dc.setDatamodelRefId(datamodelId);
            columns.add(dc);
        });
        return columns;
    }

}
